﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace IOP.Extension.Json
{
    /// <summary>
    /// 携带多枚举值的JSON转换器
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class JsonConverterEnumArray<T> : JsonConverter<T>
    {
        /// <summary>
        /// 是否允许转换
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public override bool CanConvert(Type type)
        {
            if (type.IsArray)
            {
                string tName = type.FullName.Replace("[]", string.Empty);
                Type oType = type.Assembly.GetType(tName);
                return oType.IsEnum;
            }
            return type.IsEnum;
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="typeToConvert"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (typeToConvert.IsArray)
            {
                string tName = typeToConvert.FullName.Replace("[]", string.Empty);
                Type oType = typeToConvert.Assembly.GetType(tName);
                List<Expression> expressions = new List<Expression>();
                if (reader.TokenType == JsonTokenType.StartArray)
                {
                    while (true)
                    {
                        reader.Read();
                        if (reader.TokenType == JsonTokenType.EndArray) break;
                        object e = GetEnum(ref reader, oType);
                        Expression c = Expression.Constant(e, oType);
                        expressions.Add(c);
                    }
                    Expression newE = Expression.NewArrayInit(oType, expressions);
                    Expression<Func<T>> lambda = Expression.Lambda<Func<T>>(newE);
                    Func<T> data = lambda.Compile();
                    return data();
                }
                else throw new JsonException("Target type is a enum array, the json value must be array");
            }
            else if (typeToConvert.IsEnum)
            {
                T e = (T)GetEnum(ref reader, typeToConvert);
                return e;
            }
            else throw new JsonException($"not support to convert enum from {reader.TokenType.ToString()}");
        }

        /// <summary>
        /// 获取枚举
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        private object GetEnum(ref Utf8JsonReader reader, Type type)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                var d = reader.GetString();
                var e = Enum.Parse(type, d);
                return e;
            }
            else if (reader.TokenType == JsonTokenType.StartArray)
            {
                string enumString = default;
                bool first = true;
                while (true)
                {
                    reader.Read();
                    if (reader.TokenType == JsonTokenType.EndArray) break;
                    if (reader.TokenType != JsonTokenType.String) throw new JsonException("The json array value type must be string for enum");
                    string enumValue = reader.GetString();
                    if (first) first = false;
                    else enumString += ",";
                    enumString += enumValue.ToString();
                }
                var e = Enum.Parse(type, enumString);
                return e;
            }
            else throw new JsonException($"not support to convert enum from {reader.TokenType.ToString()}");
        }

        /// <summary>
        /// 写入
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="value"></param>
        /// <param name="options"></param>
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            var t = typeof(T);
            if (t.IsArray)
            {
                var aT = value as Array;
                writer.WriteStartArray();
                foreach (var v in aT)
                {
                    WriteEnum(writer, v);
                }
                writer.WriteEndArray();
            }
            else if (t.IsEnum) WriteEnum(writer, value);
            else throw new JsonException($"not support to convert enum, not support type {t.Name}");
        }

        private void WriteEnum(Utf8JsonWriter writer, object value)
        {
            string[] enumString = value.ToString().Split(',');
            if (enumString.Length == 1)
            {
                writer.WriteStringValue(enumString[0]);
                return;
            }
            else
            {
                writer.WriteStartArray();
                foreach (string enumValue in enumString)
                {
                    writer.WriteStringValue(enumValue.TrimStart());
                }
                writer.WriteEndArray();
            }
        }
    }
}
